#pragma once

#include <cstddef>
#include <array>

#include <luisa/core/stl/hash_fwd.h>
#include <luisa/core/basic_traits.h>

namespace luisa {

// vectors
namespace detail {

template<typename T, size_t N>
struct vector_alignment {
    static constexpr size_t _a = sizeof(T) * (N == 3 ? 4 : N);
    static constexpr size_t value = _a > 16u ? 16u : _a;
};
template<typename T, size_t N>
static constexpr size_t vector_alignment_v = vector_alignment<T, N>::value;

/// Vector storage only allows size of 2, 3, 4
template<typename T, std::size_t N>
struct VectorStorage {
    static_assert(always_false_v<T>, "Invalid vector storage");
};

/// Vector storage of size 2
template<typename T>
struct alignas(vector_alignment_v<T, 2>) VectorStorage<T, 2> {
    T x{};
    T y{};
    explicit constexpr VectorStorage(T s = {}) noexcept : x{s}, y{s} {}
    constexpr VectorStorage(T x, T y) noexcept : x{x}, y{y} {}
#include <luisa/core/swizzle_2.inl.h>
};

/// Vector storage of size 3
template<typename T>
struct alignas(vector_alignment_v<T, 3>) VectorStorage<T, 3> {
    T x{};
    T y{};
    T z{};
    explicit constexpr VectorStorage(T s = {}) noexcept : x{s}, y{s}, z{s} {}
    constexpr VectorStorage(T x, T y, T z) noexcept : x{x}, y{y}, z{z} {}
#include <luisa/core/swizzle_3.inl.h>
};

/// Vector storage of size 4
template<typename T>
struct alignas(vector_alignment_v<T, 4>) VectorStorage<T, 4> {
    T x{};
    T y{};
    T z{};
    T w{};
    explicit constexpr VectorStorage(T s = {}) noexcept : x{s}, y{s}, z{s}, w{s} {}
    constexpr VectorStorage(T x, T y, T z, T w) noexcept : x{x}, y{y}, z{z}, w{w} {}
#include <luisa/core/swizzle_4.inl.h>
};

}// namespace detail

/**
 * @brief Vector class
 * 
 * We only support vector of size 2, 3, 4 and type bool, float, half, 16/32/64-bit int/uint.
 * Any other kind of template parameters will fail on compilation.
 * 
 * @tparam T bool/float/int/uint
 * @tparam N 2/3/4
 */
template<typename T, std::size_t N>
struct Vector : public detail::VectorStorage<T, N> {
    using value_type = T;
    using size_type = std::size_t;
    using reference = value_type &;
    using const_reference = const value_type &;
    using Storage = detail::VectorStorage<value_type, N>;
    using Storage::VectorStorage;// introduce base class constructor function

    static_assert(is_scalar_v<value_type>, "Invalid vector type");
    static_assert(N == 2 || N == 3 || N == 4, "Invalid vector dimension");

    static constexpr size_type dimension{N};

    static Vector<value_type, N> zero() noexcept {
        return Vector<value_type, N>(static_cast<value_type>(0));
    }

    static Vector<value_type, N> one() noexcept {
        return Vector<value_type, N>(static_cast<value_type>(1));
    }

    [[nodiscard]]
    constexpr reference operator[](size_type index) noexcept {
        return (&(this->x))[index];
    }

    [[nodiscard]]
    constexpr const_reference operator[](size_type index) const noexcept {
        return (&(this->x))[index];
    }
};

template<typename T, size_t N>
struct hash<Vector<T, N>> {
    using is_avalanching = void;
    [[nodiscard]] uint64_t operator()(const Vector<T, N> &v,
                                      uint64_t seed = hash64_default_seed) const noexcept {
        return hash64(&v, sizeof(T) * N, seed);
    }
};

/**
 * @brief Make vector types of size 2, 3, 4
 *  
 * Ex. LUISA_MAKE_VECTOR_TYPES(int) -> int2, int3, int4
 */
#define LUISA_MAKE_VECTOR_TYPES(T) \
    using T##2 = Vector<T, 2>;     \
    using T##3 = Vector<T, 3>;     \
    using T##4 = Vector<T, 4>;

LUISA_MAKE_VECTOR_TYPES(bool)

LUISA_MAKE_VECTOR_TYPES(short)
LUISA_MAKE_VECTOR_TYPES(ushort)

LUISA_MAKE_VECTOR_TYPES(byte)
LUISA_MAKE_VECTOR_TYPES(ubyte)

LUISA_MAKE_VECTOR_TYPES(int)
LUISA_MAKE_VECTOR_TYPES(uint)

LUISA_MAKE_VECTOR_TYPES(ulong)
LUISA_MAKE_VECTOR_TYPES(slong)

LUISA_MAKE_VECTOR_TYPES(half)
LUISA_MAKE_VECTOR_TYPES(float)
LUISA_MAKE_VECTOR_TYPES(double)

#undef LUISA_MAKE_VECTOR_TYPES

/// Matrix only allows size of 2, 3, 4
template<typename T, size_t N>
struct Matrix {
    static_assert(always_false_v<std::integral_constant<size_t, N>>, "Invalid matrix type");
};

/// 2x2 matrix
template<typename T>
struct Matrix<T, 2> {
    using VectorType = Vector<T, 2>;
    using ElementType = T;
    VectorType cols[2];

    constexpr Matrix() noexcept
        : cols{VectorType{T{1.0}, T{0.0}}, VectorType{T{0.0}, T{1.0}}} {}

    constexpr Matrix(const VectorType c0, const VectorType c1) noexcept
        : cols{c0, c1} {}

    static constexpr Matrix eye(const T c) noexcept {
        return Matrix{
            VectorType{c, T{0.0}},
            VectorType{T{0.0}, c}};
    }

    static constexpr Matrix fill(const T c) noexcept {
        return Matrix{
            VectorType{c, c},
            VectorType{c, c}};
    }

    [[nodiscard]] constexpr VectorType &operator[](size_t i) noexcept { return cols[i]; }
    [[nodiscard]] constexpr const VectorType &operator[](size_t i) const noexcept { return cols[i]; }
};

/// 3x3 matrix
template<typename T>
struct Matrix<T, 3> {
    using VectorType = Vector<T, 3>;
    using ElementType = T;
    VectorType cols[3];

    constexpr Matrix() noexcept
        : cols{VectorType{T{1.0}, T{0.0}, T{0.0}}, VectorType{T{0.0}, T{1.0}, T{0.0}}, VectorType{T{0.0}, T{0.0}, T{1.0}}} {}

    constexpr Matrix(const VectorType c0, const VectorType c1, const VectorType c2) noexcept
        : cols{c0, c1, c2} {}

    static constexpr Matrix eye(const float c) noexcept {
        return Matrix{
            VectorType{c, T{0.0}, T{0.0}},
            VectorType{T{0.0}, c, T{0.0}},
            VectorType{T{0.0}, T{0.0}, c}};
    }

    static constexpr Matrix fill(const float c) noexcept {
        return Matrix{
            VectorType{c, c, c},
            VectorType{c, c, c},
            VectorType{c, c, c}};
    }

    [[nodiscard]] constexpr VectorType &operator[](size_t i) noexcept { return cols[i]; }
    [[nodiscard]] constexpr const VectorType &operator[](size_t i) const noexcept { return cols[i]; }
};

/// 4x4 matrix
template<typename T>
struct Matrix<T, 4> {
    using VectorType = Vector<T, 4>;
    using ElementType = T;
    VectorType cols[4];

    constexpr Matrix() noexcept
        : cols{VectorType{T{1.0}, T{0.0}, T{0.0}, T{0.0}},
               VectorType{T{0.0}, T{1.0}, T{0.0}, T{0.0}},
               VectorType{T{0.0}, T{0.0}, T{1.0}, T{0.0}},
               VectorType{T{0.0}, T{0.0}, T{0.0}, T{1.0}}} {}

    constexpr Matrix(const VectorType c0, const VectorType c1, const VectorType c2, const VectorType c3) noexcept
        : cols{c0, c1, c2, c3} {}

    static constexpr Matrix eye(const float c) noexcept {
        return Matrix{
            VectorType{c, T{0.0}, T{0.0}, T{0.0}},
            VectorType{T{0.0}, c, T{0.0}, T{0.0}},
            VectorType{T{0.0}, T{0.0}, c, T{0.0}},
            VectorType{T{0.0}, T{0.0}, T{0.0}, c}};
    }

    static constexpr Matrix fill(const float c) noexcept {
        return Matrix{
            VectorType{c, c, c, c},
            VectorType{c, c, c, c},
            VectorType{c, c, c, c},
            VectorType{c, c, c, c}};
    }

    [[nodiscard]] constexpr VectorType &operator[](size_t i) noexcept { return cols[i]; }
    [[nodiscard]] constexpr const VectorType &operator[](size_t i) const noexcept { return cols[i]; }
};

template<typename T, size_t N>
struct hash<Matrix<T, N>> {
    using is_avalanching = void;
    [[nodiscard]] uint64_t operator()(Matrix<T, N> m, uint64_t seed = hash64_default_seed) const noexcept {
        std::array<T, N * N> data{};
        for (size_t i = 0u; i < N; i++) {
            for (size_t j = 0u; j < N; j++) {
                data[i * N + j] = m[i][j];
            }
        }
        return hash64(data.data(), data.size() * sizeof(T), seed);
    }
};

using float2x2 = Matrix<float, 2>;
using float3x3 = Matrix<float, 3>;
using float4x4 = Matrix<float, 4>;
using double2x2 = Matrix<double, 2>;
using double3x3 = Matrix<double, 3>;
using double4x4 = Matrix<double, 4>;
using half2x2 = Matrix<half, 2>;
using half3x3 = Matrix<half, 3>;
using half4x4 = Matrix<half, 4>;

using basic_types = std::tuple<
    bool, float, int, uint, short, ushort, slong, ulong, half, double, byte, ubyte,
    bool2, float2, int2, uint2, short2, ushort2, byte2, ubyte2, slong2, ulong2, half2, double2,
    bool3, float3, int3, uint3, short3, ushort3, byte3, ubyte3, slong3, ulong3, half3, double3,
    bool4, float4, int4, uint4, short4, ushort4, byte4, ubyte4, slong4, ulong4, half4, double4,
    float2x2, float3x3, float4x4
    // TODO: should half matrix and double matrix become builtin-type? May break all backends
    // ,half2x2, half3x3, half4x4,
    // double2x2, double3x3, double4x4
    >;

/// any of bool2 is true
[[nodiscard]] constexpr auto any(const bool2 v) noexcept { return v.x || v.y; }
/// any of bool3 is true
[[nodiscard]] constexpr auto any(const bool3 v) noexcept { return v.x || v.y || v.z; }
/// any of bool4 is true
[[nodiscard]] constexpr auto any(const bool4 v) noexcept { return v.x || v.y || v.z || v.w; }

/// all of bool2 is true
[[nodiscard]] constexpr auto all(const bool2 v) noexcept { return v.x && v.y; }
/// all of bool3 is true
[[nodiscard]] constexpr auto all(const bool3 v) noexcept { return v.x && v.y && v.z; }
/// all of bool4 is true
[[nodiscard]] constexpr auto all(const bool4 v) noexcept { return v.x && v.y && v.z && v.w; }

/// none of bool2 is true
[[nodiscard]] constexpr auto none(const bool2 v) noexcept { return !any(v); }
/// none of bool3 is true
[[nodiscard]] constexpr auto none(const bool3 v) noexcept { return !any(v); }
/// none of bool4 is true
[[nodiscard]] constexpr auto none(const bool4 v) noexcept { return !any(v); }

}// namespace luisa

/// Unary plus operator of vector. Bool vector is not allowed.
template<typename T, size_t N, std::enable_if_t<std::negation_v<luisa::is_boolean<T>>, int> = 0>
[[nodiscard]] constexpr auto operator+(const luisa::Vector<T, N> v) noexcept { return v; }

/// Unary minus operator of vector. Bool vector is not allowed.
template<typename T, size_t N, std::enable_if_t<std::negation_v<luisa::is_boolean<T>>, int> = 0>
[[nodiscard]] constexpr auto operator-(const luisa::Vector<T, N> v) noexcept {
    using R = luisa::Vector<T, N>;
    if constexpr (N == 2) {
        return R{static_cast<T>(-v.x), static_cast<T>(-v.y)};
    } else if constexpr (N == 3) {
        return R{static_cast<T>(-v.x), static_cast<T>(-v.y), static_cast<T>(-v.z)};
    } else {
        return R{static_cast<T>(-v.x), static_cast<T>(-v.y), static_cast<T>(-v.z), static_cast<T>(-v.w)};
    }
}

/// Unary not operator of vector
template<typename T, size_t N>
[[nodiscard]] constexpr auto operator!(const luisa::Vector<T, N> v) noexcept {
    if constexpr (N == 2u) {
        return luisa::bool2{!v.x, !v.y};
    } else if constexpr (N == 3u) {
        return luisa::bool3{!v.x, !v.y, !v.z};
    } else {
        return luisa::bool4{!v.x, !v.y, !v.z, !v.w};
    }
}

/// Unary xor operator of vector. Only int vector is allowed.
template<typename T, size_t N,
         std::enable_if_t<luisa::is_integral_v<T>, int> = 0>
[[nodiscard]] constexpr auto operator~(const luisa::Vector<T, N> v) noexcept {
    using R = luisa::Vector<T, N>;
    if constexpr (N == 2) {
        return R{static_cast<T>(~v.x), static_cast<T>(~v.y)};
    } else if constexpr (N == 3) {
        return R{static_cast<T>(~v.x), static_cast<T>(~v.y), static_cast<T>(~v.z)};
    } else {
        return R{static_cast<T>(~v.x), static_cast<T>(~v.y), static_cast<T>(~v.z), static_cast<T>(~v.w)};
    }
}

/// Binary & Assignment operators
#define LUISA_MAKE_VECTOR_BINARY_OPERATOR(op, ...)                                      \
    template<typename T, size_t N, std::enable_if_t<__VA_ARGS__, int> = 0>              \
    [[nodiscard]] constexpr auto operator op(                                           \
        luisa::Vector<T, N> lhs, luisa::Vector<T, N> rhs) noexcept {                    \
        if constexpr (N == 2) {                                                         \
            return luisa::Vector<T, 2>{                                                 \
                static_cast<T>(lhs.x op rhs.x),                                         \
                static_cast<T>(lhs.y op rhs.y)};                                        \
        } else if constexpr (N == 3) {                                                  \
            return luisa::Vector<T, 3>{                                                 \
                static_cast<T>(lhs.x op rhs.x),                                         \
                static_cast<T>(lhs.y op rhs.y),                                         \
                static_cast<T>(lhs.z op rhs.z)};                                        \
        } else {                                                                        \
            return luisa::Vector<T, 4>{                                                 \
                static_cast<T>(lhs.x op rhs.x),                                         \
                static_cast<T>(lhs.y op rhs.y),                                         \
                static_cast<T>(lhs.z op rhs.z),                                         \
                static_cast<T>(lhs.w op rhs.w)};                                        \
        }                                                                               \
    }                                                                                   \
    template<typename T, size_t N, std::enable_if_t<__VA_ARGS__, int> = 0>              \
    [[nodiscard]] constexpr auto operator op(luisa::Vector<T, N> lhs, T rhs) noexcept { \
        return lhs op luisa::Vector<T, N>{rhs};                                         \
    }                                                                                   \
    template<typename T, size_t N, std::enable_if_t<__VA_ARGS__, int> = 0>              \
    [[nodiscard]] constexpr auto operator op(T lhs, luisa::Vector<T, N> rhs) noexcept { \
        return luisa::Vector<T, N>{lhs} op rhs;                                         \
    }
LUISA_MAKE_VECTOR_BINARY_OPERATOR(+, std::negation_v<luisa::is_boolean<T>>)
LUISA_MAKE_VECTOR_BINARY_OPERATOR(-, std::negation_v<luisa::is_boolean<T>>)
LUISA_MAKE_VECTOR_BINARY_OPERATOR(*, std::negation_v<luisa::is_boolean<T>>)
LUISA_MAKE_VECTOR_BINARY_OPERATOR(/, std::negation_v<luisa::is_boolean<T>>)
LUISA_MAKE_VECTOR_BINARY_OPERATOR(%, luisa::is_integral_v<T>)
LUISA_MAKE_VECTOR_BINARY_OPERATOR(<<, luisa::is_integral_v<T>)
LUISA_MAKE_VECTOR_BINARY_OPERATOR(>>, luisa::is_integral_v<T>)
LUISA_MAKE_VECTOR_BINARY_OPERATOR(|, std::negation_v<luisa::is_floating_point<T>>)
LUISA_MAKE_VECTOR_BINARY_OPERATOR(&, std::negation_v<luisa::is_floating_point<T>>)
LUISA_MAKE_VECTOR_BINARY_OPERATOR(^, std::negation_v<luisa::is_floating_point<T>>)

/// Binary logic operators
#define LUISA_MAKE_VECTOR_LOGIC_OPERATOR(op, ...)                                       \
    template<typename T, size_t N, std::enable_if_t<__VA_ARGS__, int> = 0>              \
    [[nodiscard]] constexpr auto operator op(                                           \
        luisa::Vector<T, N> lhs, luisa::Vector<T, N> rhs) noexcept {                    \
        if constexpr (N == 2) {                                                         \
            return luisa::bool2{                                                        \
                lhs.x op rhs.x,                                                         \
                lhs.y op rhs.y};                                                        \
        } else if constexpr (N == 3) {                                                  \
            return luisa::bool3{                                                        \
                lhs.x op rhs.x,                                                         \
                lhs.y op rhs.y,                                                         \
                lhs.z op rhs.z};                                                        \
        } else {                                                                        \
            return luisa::bool4{                                                        \
                lhs.x op rhs.x,                                                         \
                lhs.y op rhs.y,                                                         \
                lhs.z op rhs.z,                                                         \
                lhs.w op rhs.w};                                                        \
        }                                                                               \
    }                                                                                   \
    template<typename T, size_t N, std::enable_if_t<__VA_ARGS__, int> = 0>              \
    [[nodiscard]] constexpr auto operator op(luisa::Vector<T, N> lhs, T rhs) noexcept { \
        return lhs op luisa::Vector<T, N>{rhs};                                         \
    }                                                                                   \
    template<typename T, size_t N, std::enable_if_t<__VA_ARGS__, int> = 0>              \
    [[nodiscard]] constexpr auto operator op(T lhs, luisa::Vector<T, N> rhs) noexcept { \
        return luisa::Vector<T, N>{lhs} op rhs;                                         \
    }
LUISA_MAKE_VECTOR_LOGIC_OPERATOR(||, luisa::is_boolean_v<T>)
LUISA_MAKE_VECTOR_LOGIC_OPERATOR(&&, luisa::is_boolean_v<T>)
LUISA_MAKE_VECTOR_LOGIC_OPERATOR(==, true)
LUISA_MAKE_VECTOR_LOGIC_OPERATOR(!=, true)
LUISA_MAKE_VECTOR_LOGIC_OPERATOR(<, std::negation_v<luisa::is_boolean<T>>)
LUISA_MAKE_VECTOR_LOGIC_OPERATOR(>, std::negation_v<luisa::is_boolean<T>>)
LUISA_MAKE_VECTOR_LOGIC_OPERATOR(<=, std::negation_v<luisa::is_boolean<T>>)
LUISA_MAKE_VECTOR_LOGIC_OPERATOR(>=, std::negation_v<luisa::is_boolean<T>>)

/// Assign operators
#define LUISA_MAKE_VECTOR_ASSIGN_OPERATOR(op, ...)                         \
    template<typename T, size_t N, std::enable_if_t<__VA_ARGS__, int> = 0> \
    constexpr decltype(auto) operator op(                                  \
        luisa::Vector<T, N> &lhs, luisa::Vector<T, N> rhs) noexcept {      \
        lhs.x op rhs.x;                                                    \
        lhs.y op rhs.y;                                                    \
        if constexpr (N >= 3) { lhs.z op rhs.z; }                          \
        if constexpr (N == 4) { lhs.w op rhs.w; }                          \
        return (lhs);                                                      \
    }                                                                      \
    template<typename T, size_t N, std::enable_if_t<__VA_ARGS__, int> = 0> \
    constexpr decltype(auto) operator op(                                  \
        luisa::Vector<T, N> &lhs, T rhs) noexcept {                        \
        return (lhs op luisa::Vector<T, N>{rhs});                          \
    }
LUISA_MAKE_VECTOR_ASSIGN_OPERATOR(+=, std::negation_v<luisa::is_boolean<T>>)
LUISA_MAKE_VECTOR_ASSIGN_OPERATOR(-=, std::negation_v<luisa::is_boolean<T>>)
LUISA_MAKE_VECTOR_ASSIGN_OPERATOR(*=, std::negation_v<luisa::is_boolean<T>>)
LUISA_MAKE_VECTOR_ASSIGN_OPERATOR(/=, std::negation_v<luisa::is_boolean<T>>)
LUISA_MAKE_VECTOR_ASSIGN_OPERATOR(%=, luisa::is_integral_v<T>)
LUISA_MAKE_VECTOR_ASSIGN_OPERATOR(<<=, luisa::is_integral_v<T>)
LUISA_MAKE_VECTOR_ASSIGN_OPERATOR(>>=, luisa::is_integral_v<T>)
LUISA_MAKE_VECTOR_ASSIGN_OPERATOR(|=, std::negation_v<luisa::is_floating_point<T>>)
LUISA_MAKE_VECTOR_ASSIGN_OPERATOR(&=, std::negation_v<luisa::is_floating_point<T>>)
LUISA_MAKE_VECTOR_ASSIGN_OPERATOR(^=, std::negation_v<luisa::is_floating_point<T>>)

#undef LUISA_MAKE_VECTOR_BINARY_OPERATOR
#undef LUISA_MAKE_VECTOR_LOGIC_OPERATOR
#undef LUISA_MAKE_VECTOR_ASSIGN_OPERATOR

/// float2x2 multiplied by float
[[nodiscard]] constexpr auto operator*(const luisa::float2x2 m, float s) noexcept {
    return luisa::float2x2{m[0] * s, m[1] * s};
}

/// float2x2 multiplied by float
[[nodiscard]] constexpr auto operator*(float s, const luisa::float2x2 m) noexcept {
    return m * s;
}

/// float2x2 divided by float
[[nodiscard]] constexpr auto operator/(const luisa::float2x2 m, float s) noexcept {
    return m * (1.0f / s);
}

/// float2x2 dot float2
[[nodiscard]] constexpr auto operator*(const luisa::float2x2 m, const luisa::float2 v) noexcept {
    return v.x * m[0] + v.y * m[1];
}

/// float2x2 multiply(matmul)
[[nodiscard]] constexpr auto operator*(const luisa::float2x2 lhs, const luisa::float2x2 rhs) noexcept {
    return luisa::float2x2{lhs * rhs[0], lhs * rhs[1]};
}

/// float2x2 plus
[[nodiscard]] constexpr auto operator+(const luisa::float2x2 lhs, const luisa::float2x2 rhs) noexcept {
    return luisa::float2x2{lhs[0] + rhs[0], lhs[1] + rhs[1]};
}

/// float2x2 minus
[[nodiscard]] constexpr auto operator-(const luisa::float2x2 lhs, const luisa::float2x2 rhs) noexcept {
    return luisa::float2x2{lhs[0] - rhs[0], lhs[1] - rhs[1]};
}

/// float3x3 multiplied by float
[[nodiscard]] constexpr auto operator*(const luisa::float3x3 m, float s) noexcept {
    return luisa::float3x3{m[0] * s, m[1] * s, m[2] * s};
}

/// float3x3 multiplied by float
[[nodiscard]] constexpr auto operator*(float s, const luisa::float3x3 m) noexcept {
    return m * s;
}

/// float 3x3 divided by float
[[nodiscard]] constexpr auto operator/(const luisa::float3x3 m, float s) noexcept {
    return m * (1.0f / s);
}

/// float3x3 dot float3
[[nodiscard]] constexpr auto operator*(const luisa::float3x3 m, const luisa::float3 v) noexcept {
    return v.x * m[0] + v.y * m[1] + v.z * m[2];
}

/// float3x3 multiply(matmul)
[[nodiscard]] constexpr auto operator*(const luisa::float3x3 lhs, const luisa::float3x3 rhs) noexcept {
    return luisa::float3x3{lhs * rhs[0], lhs * rhs[1], lhs * rhs[2]};
}

/// float3x3 plus
[[nodiscard]] constexpr auto operator+(const luisa::float3x3 lhs, const luisa::float3x3 rhs) noexcept {
    return luisa::float3x3{lhs[0] + rhs[0], lhs[1] + rhs[1], lhs[2] + rhs[2]};
}

/// float3x3 minus
[[nodiscard]] constexpr auto operator-(const luisa::float3x3 lhs, const luisa::float3x3 rhs) noexcept {
    return luisa::float3x3{lhs[0] - rhs[0], lhs[1] - rhs[1], lhs[2] - rhs[2]};
}

/// float4x4 multiplied by float
[[nodiscard]] constexpr auto operator*(const luisa::float4x4 m, float s) noexcept {
    return luisa::float4x4{m[0] * s, m[1] * s, m[2] * s, m[3] * s};
}

/// float4x4 multiplied by float
[[nodiscard]] constexpr auto operator*(float s, const luisa::float4x4 m) noexcept {
    return m * s;
}

/// float4x4 divided by float
[[nodiscard]] constexpr auto operator/(const luisa::float4x4 m, float s) noexcept {
    return m * (1.0f / s);
}

/// floa4x4 dot float4
[[nodiscard]] constexpr auto operator*(const luisa::float4x4 m, const luisa::float4 v) noexcept {
    return v.x * m[0] + v.y * m[1] + v.z * m[2] + v.w * m[3];
}

/// float4x4 multiply(matmul)
[[nodiscard]] constexpr auto operator*(const luisa::float4x4 lhs, const luisa::float4x4 rhs) noexcept {
    return luisa::float4x4{lhs * rhs[0], lhs * rhs[1], lhs * rhs[2], lhs * rhs[3]};
}

/// float4x4 plus
[[nodiscard]] constexpr auto operator+(const luisa::float4x4 lhs, const luisa::float4x4 rhs) noexcept {
    return luisa::float4x4{lhs[0] + rhs[0], lhs[1] + rhs[1], lhs[2] + rhs[2], lhs[3] + rhs[3]};
}

/// float4x4 minus
[[nodiscard]] constexpr auto operator-(const luisa::float4x4 lhs, const luisa::float4x4 rhs) noexcept {
    return luisa::float4x4{lhs[0] - rhs[0], lhs[1] - rhs[1], lhs[2] - rhs[2], lhs[3] - rhs[3]};
}

/// double2x2 multiplied by double
[[nodiscard]] constexpr auto operator*(const luisa::double2x2 m, double s) noexcept {
    return luisa::double2x2{m[0] * s, m[1] * s};
}

/// double2x2 multiplied by double
[[nodiscard]] constexpr auto operator*(double s, const luisa::double2x2 m) noexcept {
    return m * s;
}

/// double2x2 divided by double
[[nodiscard]] constexpr auto operator/(const luisa::double2x2 m, double s) noexcept {
    return m * (1.0f / s);
}

/// double2x2 dot double2
[[nodiscard]] constexpr auto operator*(const luisa::double2x2 m, const luisa::double2 v) noexcept {
    return v.x * m[0] + v.y * m[1];
}

/// double2x2 multiply(matmul)
[[nodiscard]] constexpr auto operator*(const luisa::double2x2 lhs, const luisa::double2x2 rhs) noexcept {
    return luisa::double2x2{lhs * rhs[0], lhs * rhs[1]};
}

/// double2x2 plus
[[nodiscard]] constexpr auto operator+(const luisa::double2x2 lhs, const luisa::double2x2 rhs) noexcept {
    return luisa::double2x2{lhs[0] + rhs[0], lhs[1] + rhs[1]};
}

/// double2x2 minus
[[nodiscard]] constexpr auto operator-(const luisa::double2x2 lhs, const luisa::double2x2 rhs) noexcept {
    return luisa::double2x2{lhs[0] - rhs[0], lhs[1] - rhs[1]};
}

/// double3x3 multiplied by double
[[nodiscard]] constexpr auto operator*(const luisa::double3x3 m, double s) noexcept {
    return luisa::double3x3{m[0] * s, m[1] * s, m[2] * s};
}

/// double3x3 multiplied by double
[[nodiscard]] constexpr auto operator*(double s, const luisa::double3x3 m) noexcept {
    return m * s;
}

/// double 3x3 divided by double
[[nodiscard]] constexpr auto operator/(const luisa::double3x3 m, double s) noexcept {
    return m * (1.0f / s);
}

/// double3x3 dot double3
[[nodiscard]] constexpr auto operator*(const luisa::double3x3 m, const luisa::double3 v) noexcept {
    return v.x * m[0] + v.y * m[1] + v.z * m[2];
}

/// double3x3 multiply(matmul)
[[nodiscard]] constexpr auto operator*(const luisa::double3x3 lhs, const luisa::double3x3 rhs) noexcept {
    return luisa::double3x3{lhs * rhs[0], lhs * rhs[1], lhs * rhs[2]};
}

/// double3x3 plus
[[nodiscard]] constexpr auto operator+(const luisa::double3x3 lhs, const luisa::double3x3 rhs) noexcept {
    return luisa::double3x3{lhs[0] + rhs[0], lhs[1] + rhs[1], lhs[2] + rhs[2]};
}

/// double3x3 minus
[[nodiscard]] constexpr auto operator-(const luisa::double3x3 lhs, const luisa::double3x3 rhs) noexcept {
    return luisa::double3x3{lhs[0] - rhs[0], lhs[1] - rhs[1], lhs[2] - rhs[2]};
}

/// double4x4 multiplied by double
[[nodiscard]] constexpr auto operator*(const luisa::double4x4 m, double s) noexcept {
    return luisa::double4x4{m[0] * s, m[1] * s, m[2] * s, m[3] * s};
}

/// double4x4 multiplied by double
[[nodiscard]] constexpr auto operator*(double s, const luisa::double4x4 m) noexcept {
    return m * s;
}

/// double4x4 divided by double
[[nodiscard]] constexpr auto operator/(const luisa::double4x4 m, double s) noexcept {
    return m * (1.0f / s);
}

/// floa4x4 dot double4
[[nodiscard]] constexpr auto operator*(const luisa::double4x4 m, const luisa::double4 v) noexcept {
    return v.x * m[0] + v.y * m[1] + v.z * m[2] + v.w * m[3];
}

/// double4x4 multiply(matmul)
[[nodiscard]] constexpr auto operator*(const luisa::double4x4 lhs, const luisa::double4x4 rhs) noexcept {
    return luisa::double4x4{lhs * rhs[0], lhs * rhs[1], lhs * rhs[2], lhs * rhs[3]};
}

/// double4x4 plus
[[nodiscard]] constexpr auto operator+(const luisa::double4x4 lhs, const luisa::double4x4 rhs) noexcept {
    return luisa::double4x4{lhs[0] + rhs[0], lhs[1] + rhs[1], lhs[2] + rhs[2], lhs[3] + rhs[3]};
}

/// double4x4 minus
[[nodiscard]] constexpr auto operator-(const luisa::double4x4 lhs, const luisa::double4x4 rhs) noexcept {
    return luisa::double4x4{lhs[0] - rhs[0], lhs[1] - rhs[1], lhs[2] - rhs[2], lhs[3] - rhs[3]};
}

namespace luisa {

/// make_typeN definitions
#define LUISA_MAKE_TYPE_N(type)                                                                                              \
    [[nodiscard]] constexpr auto make_##type##2(type s = {}) noexcept { return type##2(s); }                                 \
    [[nodiscard]] constexpr auto make_##type##2(type x, type y) noexcept { return type##2(x, y); }                           \
    template<typename T>                                                                                                     \
    [[nodiscard]] constexpr auto make_##type##2(Vector<T, 2> v) noexcept {                                                   \
        return type##2(                                                                                                      \
            static_cast<type>(v.x),                                                                                          \
            static_cast<type>(v.y));                                                                                         \
    }                                                                                                                        \
    [[nodiscard]] constexpr auto make_##type##2(type##3 v) noexcept { return type##2(v.x, v.y); }                            \
    [[nodiscard]] constexpr auto make_##type##2(type##4 v) noexcept { return type##2(v.x, v.y); }                            \
                                                                                                                             \
    [[nodiscard]] constexpr auto make_##type##3(type s = {}) noexcept { return type##3(s); }                                 \
    [[nodiscard]] constexpr auto make_##type##3(type x, type y, type z) noexcept { return type##3(x, y, z); }                \
    template<typename T>                                                                                                     \
    [[nodiscard]] constexpr auto make_##type##3(Vector<T, 3> v) noexcept {                                                   \
        return type##3(                                                                                                      \
            static_cast<type>(v.x),                                                                                          \
            static_cast<type>(v.y),                                                                                          \
            static_cast<type>(v.z));                                                                                         \
    }                                                                                                                        \
    [[nodiscard]] constexpr auto make_##type##3(type##2 v, type z) noexcept { return type##3(v.x, v.y, z); }                 \
    [[nodiscard]] constexpr auto make_##type##3(type x, type##2 v) noexcept { return type##3(x, v.x, v.y); }                 \
    [[nodiscard]] constexpr auto make_##type##3(type##4 v) noexcept { return type##3(v.x, v.y, v.z); }                       \
                                                                                                                             \
    [[nodiscard]] constexpr auto make_##type##4(type s = {}) noexcept { return type##4(s); }                                 \
    [[nodiscard]] constexpr auto make_##type##4(type x, type y, type z, type w) noexcept { return type##4(x, y, z, w); }     \
    template<typename T>                                                                                                     \
    [[nodiscard]] constexpr auto make_##type##4(Vector<T, 4> v) noexcept {                                                   \
        return type##4(                                                                                                      \
            static_cast<type>(v.x),                                                                                          \
            static_cast<type>(v.y),                                                                                          \
            static_cast<type>(v.z),                                                                                          \
            static_cast<type>(v.w));                                                                                         \
    }                                                                                                                        \
    [[nodiscard]] constexpr auto make_##type##4(type##2 v, type z, type w) noexcept { return type##4(v.x, v.y, z, w); }      \
    [[nodiscard]] constexpr auto make_##type##4(type x, type##2 v, type w) noexcept { return type##4(x, v.x, v.y, w); }      \
    [[nodiscard]] constexpr auto make_##type##4(type x, type y, type##2 v) noexcept { return type##4(x, y, v.x, v.y); }      \
    [[nodiscard]] constexpr auto make_##type##4(type##2 xy, type##2 zw) noexcept { return type##4(xy.x, xy.y, zw.x, zw.y); } \
    [[nodiscard]] constexpr auto make_##type##4(type##3 v, type w) noexcept { return type##4(v.x, v.y, v.z, w); }            \
    [[nodiscard]] constexpr auto make_##type##4(type x, type##3 v) noexcept { return type##4(x, v.x, v.y, v.z); }

LUISA_MAKE_TYPE_N(bool)
LUISA_MAKE_TYPE_N(float)
LUISA_MAKE_TYPE_N(int)
LUISA_MAKE_TYPE_N(uint)
LUISA_MAKE_TYPE_N(byte)
LUISA_MAKE_TYPE_N(ubyte)
LUISA_MAKE_TYPE_N(short)
LUISA_MAKE_TYPE_N(ushort)
LUISA_MAKE_TYPE_N(slong)
LUISA_MAKE_TYPE_N(ulong)
LUISA_MAKE_TYPE_N(half)
LUISA_MAKE_TYPE_N(double)

#undef LUISA_MAKE_TYPE_N

/// make float2x2
[[nodiscard]] constexpr auto make_float2x2(float s = 1.0f) noexcept {
    return float2x2{float2{s, 0.0f},
                    float2{0.0f, s}};
}

/// make float2x2
[[nodiscard]] constexpr auto make_float2x2(
    float m00, float m01,
    float m10, float m11) noexcept {
    return float2x2{float2{m00, m01},
                    float2{m10, m11}};
}

/// make float2x2
[[nodiscard]] constexpr auto make_float2x2(float2 c0, float2 c1) noexcept {
    return float2x2{c0, c1};
}

/// make float2x2
[[nodiscard]] constexpr auto make_float2x2(float2x2 m) noexcept {
    return m;
}

/// make float2x2
[[nodiscard]] constexpr auto make_float2x2(float3x3 m) noexcept {
    return float2x2{float2{m[0].x, m[0].y},
                    float2{m[1].x, m[1].y}};
}

/// make float2x2
[[nodiscard]] constexpr auto make_float2x2(float4x4 m) noexcept {
    return float2x2{float2{m[0].x, m[0].y},
                    float2{m[1].x, m[1].y}};
}

/// make float3x3
[[nodiscard]] constexpr auto make_float3x3(float s = 1.0f) noexcept {
    return float3x3{float3{s, 0.0f, 0.0f},
                    float3{0.0f, s, 0.0f},
                    float3{0.0f, 0.0f, s}};
}

/// make float3x3
[[nodiscard]] constexpr auto make_float3x3(float3 c0, float3 c1, float3 c2) noexcept {
    return float3x3{c0, c1, c2};
}

/// make float3x3
[[nodiscard]] constexpr auto make_float3x3(
    float m00, float m01, float m02,
    float m10, float m11, float m12,
    float m20, float m21, float m22) noexcept {
    return float3x3{float3{m00, m01, m02},
                    float3{m10, m11, m12},
                    float3{m20, m21, m22}};
}

/// make float3x3
[[nodiscard]] constexpr auto make_float3x3(float2x2 m) noexcept {
    return float3x3{make_float3(m[0], 0.0f),
                    make_float3(m[1], 0.0f),
                    make_float3(0.f, 0.f, 1.0f)};
}

/// make float3x3
[[nodiscard]] constexpr auto make_float3x3(float3x3 m) noexcept {
    return m;
}

/// make float3x3
[[nodiscard]] constexpr auto make_float3x3(float4x4 m) noexcept {
    return float3x3{make_float3(m[0]),
                    make_float3(m[1]),
                    make_float3(m[2])};
}

/// make float4x4
[[nodiscard]] constexpr auto make_float4x4(float s = 1.0f) noexcept {
    return float4x4{float4{s, 0.0f, 0.0f, 0.0f},
                    float4{0.0f, s, 0.0f, 0.0f},
                    float4{0.0f, 0.0f, s, 0.0f},
                    float4{0.0f, 0.0f, 0.0f, s}};
}

/// make float4x4
[[nodiscard]] constexpr auto make_float4x4(float4 c0, float4 c1, float4 c2, float4 c3) noexcept {
    return float4x4{c0, c1, c2, c3};
}

/// make float4x4
[[nodiscard]] constexpr auto make_float4x4(
    float m00, float m01, float m02, float m03,
    float m10, float m11, float m12, float m13,
    float m20, float m21, float m22, float m23,
    float m30, float m31, float m32, float m33) noexcept {
    return float4x4{float4{m00, m01, m02, m03},
                    float4{m10, m11, m12, m13},
                    float4{m20, m21, m22, m23},
                    float4{m30, m31, m32, m33}};
}

/// make float4x4
[[nodiscard]] constexpr auto make_float4x4(float2x2 m) noexcept {
    return float4x4{make_float4(m[0], 0.0f, 0.0f),
                    make_float4(m[1], 0.0f, 0.0f),
                    float4{0.0f, 0.0f, 1.0f, 0.0f},
                    float4{0.0f, 0.0f, 0.0f, 1.0f}};
}

/// make float4x4
[[nodiscard]] constexpr auto make_float4x4(float3x3 m) noexcept {
    return float4x4{make_float4(m[0], 0.0f),
                    make_float4(m[1], 0.0f),
                    make_float4(m[2], 0.0f),
                    float4{0.0f, 0.0f, 0.0f, 1.0f}};
}

/// make float4x4
[[nodiscard]] constexpr auto make_float4x4(float4x4 m) noexcept {
    return m;
}
[[nodiscard]] constexpr auto make_double2x2(double s = 1.0) noexcept {
    return double2x2{double2{s, 0.0},
                     double2{0.0, s}};
}

/// make double2x2
[[nodiscard]] constexpr auto make_double2x2(
    double m00, double m01,
    double m10, double m11) noexcept {
    return double2x2{double2{m00, m01},
                     double2{m10, m11}};
}

/// make double2x2
[[nodiscard]] constexpr auto make_double2x2(double2 c0, double2 c1) noexcept {
    return double2x2{c0, c1};
}

/// make double2x2
[[nodiscard]] constexpr auto make_double2x2(double2x2 m) noexcept {
    return m;
}

/// make double2x2
[[nodiscard]] constexpr auto make_double2x2(double3x3 m) noexcept {
    return double2x2{double2{m[0].x, m[0].y},
                     double2{m[1].x, m[1].y}};
}

/// make double2x2
[[nodiscard]] constexpr auto make_double2x2(double4x4 m) noexcept {
    return double2x2{double2{m[0].x, m[0].y},
                     double2{m[1].x, m[1].y}};
}

/// make double3x3
[[nodiscard]] constexpr auto make_double3x3(double s = 1.0) noexcept {
    return double3x3{double3{s, 0.0, 0.0},
                     double3{0.0, s, 0.0},
                     double3{0.0, 0.0, s}};
}

/// make double3x3
[[nodiscard]] constexpr auto make_double3x3(double3 c0, double3 c1, double3 c2) noexcept {
    return double3x3{c0, c1, c2};
}

/// make double3x3
[[nodiscard]] constexpr auto make_double3x3(
    double m00, double m01, double m02,
    double m10, double m11, double m12,
    double m20, double m21, double m22) noexcept {
    return double3x3{double3{m00, m01, m02},
                     double3{m10, m11, m12},
                     double3{m20, m21, m22}};
}

/// make double3x3
[[nodiscard]] constexpr auto make_double3x3(double2x2 m) noexcept {
    return double3x3{make_double3(m[0], 0.0),
                     make_double3(m[1], 0.0),
                     make_double3(0.f, 0.f, 1.0)};
}

/// make double3x3
[[nodiscard]] constexpr auto make_double3x3(double3x3 m) noexcept {
    return m;
}

/// make double3x3
[[nodiscard]] constexpr auto make_double3x3(double4x4 m) noexcept {
    return double3x3{make_double3(m[0].xyz()),
                     make_double3(m[1].xyz()),
                     make_double3(m[2].xyz())};
}

/// make double4x4
[[nodiscard]] constexpr auto make_double4x4(double s = 1.0) noexcept {
    return double4x4{double4{s, 0.0, 0.0, 0.0},
                     double4{0.0, s, 0.0, 0.0},
                     double4{0.0, 0.0, s, 0.0},
                     double4{0.0, 0.0, 0.0, s}};
}

/// make double4x4
[[nodiscard]] constexpr auto make_double4x4(double4 c0, double4 c1, double4 c2, double4 c3) noexcept {
    return double4x4{c0, c1, c2, c3};
}

/// make double4x4
[[nodiscard]] constexpr auto make_double4x4(
    double m00, double m01, double m02, double m03,
    double m10, double m11, double m12, double m13,
    double m20, double m21, double m22, double m23,
    double m30, double m31, double m32, double m33) noexcept {
    return double4x4{double4{m00, m01, m02, m03},
                     double4{m10, m11, m12, m13},
                     double4{m20, m21, m22, m23},
                     double4{m30, m31, m32, m33}};
}

/// make double4x4
[[nodiscard]] constexpr auto make_double4x4(double2x2 m) noexcept {
    return double4x4{make_double4(m[0], 0.0, 0.0),
                     make_double4(m[1], 0.0, 0.0),
                     double4{0.0, 0.0, 1.0, 0.0},
                     double4{0.0, 0.0, 0.0, 1.0}};
}

/// make double4x4
[[nodiscard]] constexpr auto make_double4x4(double3x3 m) noexcept {
    return double4x4{make_double4(m[0], 0.0),
                     make_double4(m[1], 0.0),
                     make_double4(m[2], 0.0),
                     double4{0.0, 0.0, 0.0, 1.0}};
}
[[nodiscard]] inline auto make_half2x2(half s = (half)1.0f) noexcept {
    return half2x2{half2{s, half(0.0)},
                   half2{half(0.0), s}};
}

/// make half2x2
[[nodiscard]] constexpr auto make_half2x2(
    half m00, half m01,
    half m10, half m11) noexcept {
    return half2x2{half2{m00, m01},
                   half2{m10, m11}};
}

/// make half2x2
[[nodiscard]] constexpr auto make_half2x2(half2 c0, half2 c1) noexcept {
    return half2x2{c0, c1};
}

/// make half2x2
[[nodiscard]] constexpr auto make_half2x2(half2x2 m) noexcept {
    return m;
}

/// make half2x2
[[nodiscard]] constexpr auto make_half2x2(half3x3 m) noexcept {
    return half2x2{half2{m[0].x, m[0].y},
                   half2{m[1].x, m[1].y}};
}

/// make half2x2
[[nodiscard]] constexpr auto make_half2x2(half4x4 m) noexcept {
    return half2x2{half2{m[0].x, m[0].y},
                   half2{m[1].x, m[1].y}};
}

/// make half3x3
[[nodiscard]] inline auto make_half3x3(half s = (half)1.0f) noexcept {
    return half3x3{half3{s, half(0.0), half(0.0)},
                   half3{half(0.0), s, half(0.0)},
                   half3{half(0.0), half(0.0), s}};
}

/// make half3x3
[[nodiscard]] constexpr auto make_half3x3(half3 c0, half3 c1, half3 c2) noexcept {
    return half3x3{c0, c1, c2};
}

/// make half3x3
[[nodiscard]] constexpr auto make_half3x3(
    half m00, half m01, half m02,
    half m10, half m11, half m12,
    half m20, half m21, half m22) noexcept {
    return half3x3{half3{m00, m01, m02},
                   half3{m10, m11, m12},
                   half3{m20, m21, m22}};
}

/// make half3x3
[[nodiscard]] inline auto make_half3x3(half2x2 m) noexcept {
    return half3x3{make_half3(m[0], half(0.0)),
                   make_half3(m[1], half(0.0)),
                   make_half3(half(0.0), half(0.0), half(1.0))};
}

/// make half3x3
[[nodiscard]] constexpr auto make_half3x3(half3x3 m) noexcept {
    return m;
}

/// make half3x3
[[nodiscard]] constexpr auto make_half3x3(half4x4 m) noexcept {
    return half3x3{make_half3(m[0]),
                   make_half3(m[1]),
                   make_half3(m[2])};
}

/// make half4x4
[[nodiscard]] inline auto make_half4x4(half s = (half)1.0f) noexcept {
    return half4x4{half4{s, half(0.0), half(0.0), half(0.0)},
                   half4{half(0.0), s, half(0.0), half(0.0)},
                   half4{half(0.0), half(0.0), s, half(0.0)},
                   half4{half(0.0), half(0.0), half(0.0), s}};
}

/// make half4x4
[[nodiscard]] constexpr auto make_half4x4(half4 c0, half4 c1, half4 c2, half4 c3) noexcept {
    return half4x4{c0, c1, c2, c3};
}

/// make half4x4
[[nodiscard]] constexpr auto make_half4x4(
    half m00, half m01, half m02, half m03,
    half m10, half m11, half m12, half m13,
    half m20, half m21, half m22, half m23,
    half m30, half m31, half m32, half m33) noexcept {
    return half4x4{half4{m00, m01, m02, m03},
                   half4{m10, m11, m12, m13},
                   half4{m20, m21, m22, m23},
                   half4{m30, m31, m32, m33}};
}

/// make half4x4
[[nodiscard]] inline auto make_half4x4(half2x2 m) noexcept {
    return half4x4{make_half4(m[0], half(0.0), half(0.0)),
                   make_half4(m[1], half(0.0), half(0.0)),
                   half4{half(0.0), half(0.0), half(1.0), half(0.0)},
                   half4{half(0.0), half(0.0), half(0.0), half(1.0)}};
}

/// make half4x4
[[nodiscard]] inline auto make_half4x4(half3x3 m) noexcept {
    return half4x4{make_half4(m[0], half(0.0)),
                   make_half4(m[1], half(0.0)),
                   make_half4(m[2], half(0.0)),
                   half4{half(0.0), half(0.0), half(0.0), half(1.0)}};
}

/// make half4x4
[[nodiscard]] constexpr auto make_half4x4(half4x4 m) noexcept {
    return m;
}
/// make double4x4
[[nodiscard]] constexpr auto make_double4x4(double4x4 m) noexcept {
    return m;
}
}// namespace luisa

//template<size_t N>
//constexpr auto operator*(std::array<float, N> a, float s) noexcept {
//    std::array<float, N> r;
//    for (auto i = 0u; i < N; i++) { r[i] = a[i] * s; }
//    return a;
//}
//
//template<size_t N>
//constexpr auto operator*(float s, std::array<float, N> a) noexcept {
//    return a * s;
//}
//
//template<size_t N>
//constexpr auto operator*(std::array<float, N> lhs, std::array<float, N> rhs) noexcept {
//    std::array<float, N> r;
//    for (auto i = 0u; i < N; i++) { r[i] = lhs[i] * rhs[i]; }
//    return r;
//}
//
//template<size_t N>
//constexpr auto operator+(std::array<float, N> lhs, std::array<float, N> rhs) noexcept {
//    std::array<float, N> r;
//    for (auto i = 0u; i < N; i++) { r[i] = lhs[i] + rhs[i]; }
//    return r;
//}
//
//template<size_t N>
//constexpr auto operator-(std::array<float, N> lhs, std::array<float, N> rhs) noexcept {
//    std::array<float, N> r;
//    for (auto i = 0u; i < N; i++) { r[i] = lhs[i] - rhs[i]; }
//    return r;
//}
//
//template<size_t M, size_t N>
//constexpr auto operator*(std::array<std::array<float, M>, N> m, float s) noexcept {
//    std::array<std::array<float, M>, N> r;
//    for (auto i = 0u; i < N; i++) { r[i] = m[i] * s; }
//}
//
//template<size_t M, size_t N>
//constexpr auto operator*(float s, std::array<std::array<float, M>, N> m) noexcept {
//    return m * s;
//}
//
//template<size_t M, size_t N>
//constexpr auto operator*(std::array<std::array<float, M>, N> m, std::array<float, N> v) noexcept {
//    std::array<float, N> r{};
//    for (auto i = 0u; i < N; i++) { r = r + m[i] * v[i]; }
//}
//
//template<size_t M, size_t N, size_t P>
//constexpr auto operator*(std::array<std::array<float, M>, N> lhs, std::array<std::array<float, N>, P> rhs) noexcept {
//    std::array<std::array<float, M>, P> r;
//    for (auto i = 0u; i < P; i++) { r[i] = lhs * rhs[i]; }
//    return r;
//}
//
//template<size_t M, size_t N>
//constexpr auto operator+(std::array<std::array<float, M>, N> lhs, std::array<std::array<float, M>, N> rhs) noexcept {
//    std::array<std::array<float, M>, N> r;
//    for (auto i = 0u; i < N; i++) { r[i] = lhs + rhs[i]; }
//    return r;
//}
//
//template<size_t M, size_t N>
//constexpr auto operator-(std::array<std::array<float, M>, N> lhs, std::array<std::array<float, M>, N> rhs) noexcept {
//    std::array<std::array<float, M>, N> r;
//    for (auto i = 0u; i < N; i++) { r[i] = lhs - rhs[i]; }
//    return r;
//}
